<?php
/**
 * This class is one of the classes we mainly use for manipulating objects related to a database.<br/>
 * The class provides all functionalities for manipulating a database exept ones for building complex SQL queries.
 * 
 * @package lib.famteam.phporm
 * @author polikeiji
 * @static
 *
 */
class ORMapper {

	/**
	 * Create a table in the target database.<br/>
	 * The table is created class definition of the passed object.<br/>
	 * We have to name the property names along the naming convention for indicating the column types.
	 * 
	 * @param object $object An object related to database.
	 * @return bool
	 */
	public static function setup($object) {
		return DatabaseManager::doUpdateQuery(ORMapperQuery::getCreateTableQuery($object));
	}
	
	public static function getConnection() {
		return DatabaseManager::getDatabaseConnection();
	}
	public static function closeConnection($connection) {
		return DatabaseManager::closeDatabaseConnection($connection);
	}
	
	/**
	 * This method is useful for maintenance of a database.
	 * 
	 * @param object $object
	 * @param str $fromTable
	 * @param str $toTable
	 * @return bool
	 */
	public static function dataMoveBetweenTables($object, $fromTable, $toTable) {
		$connection = DatabaseManager::getDatabaseConnection();
		$objectArray = ORMapper::selectBySQL($object, ORMapperQuery::getDataMoveSelectQuery($fromTable, $connection), $connection);
		foreach ($objectArray as $nowObject) {
			if (DatabaseManager::doUpdateQueryWithConnection(ORMapperQuery::getDataMoveInsertQuery($nowObject, $toTable, $connection), $connection)) {
				
			}
			else {
				DatabaseManager::closeDatabaseConnection($connection);
				return false;;
			}
		}
		DatabaseManager::closeDatabaseConnection($connection);
		return true;
	}

	/**
	 * Update a table corresponded to the passed object.
	 * 
	 * @param object $object
	 * @param ORMapperQueryWhere $where
	 * @param str $formula Formulas for update columns. E.g. column1='value1', column2=number2
	 * @return number Number of updated columns
	 */
	public static function update($object, $where = null, $formula = null, $connection = null) {
		$isConnectionProvided = true;
		if ($connection == null) {
			$connection = DatabaseManager::getDatabaseConnection();
			$isConnectionProvided = false;
		}
//		echo 'Query: ' . ORMapperQuery::getUpdateQuery($object, $connection, $where, $formula) . "\n";
//		Logger::log(ORMapperQuery::getUpdateQuery($object, $connection, $where, $formula));
//		Logger::print_r($object);
		$result = DatabaseManager::doUpdateQueryWithConnection(ORMapperQuery::getUpdateQuery($object, $connection, $where, $formula), $connection);
		if (!$isConnectionProvided) {
			DatabaseManager::closeDatabaseConnection($connection);
		}
		return $result;
	}

	/**
	 * Update multiple objects.<br/>
	 * 
	 * @param array $objectArray
	 * @return bool
	 */
	public static function updateArrayObject($objectArray) {
		$connection = DatabaseManager::getDatabaseConnection();
		foreach ($objectArray as $object) {
			$result = DatabaseManager::doUpdateQueryWithConnection(ORMapperQuery::getUpdateQuery($object, $connection), $connection);
			if (!$result) {
				break;
			}
		}
		DatabaseManager::closeDatabaseConnection($connection);
		return $result;
	}

	/**
	 * Insert a new object.
	 * 
	 * @param object $object
	 * @return object A inserted object which have auto incremental unique key in its id property
	 */
	public static function insert($object, $connection = null, $ignore = false) {
		$isConnectionProvided = true;
		if ($connection == null) {
			$connection = DatabaseManager::getDatabaseConnection();
			$isConnectionProvided = false;
		}
		if ($connection != null) {
			if (DatabaseManager::doUpdateQueryWithConnection(ORMapperQuery::getInsertQuery($object, $connection, true, $ignore), $connection) &&
				($id = DatabaseManager::getLastInsertID($connection)) != null) {

				if (!$isConnectionProvided) {
					DatabaseManager::closeDatabaseConnection($connection);
				}
				return $object;
			}
			else {
				return null;
			}
		}
		else {
			return null;
		}
	}

	public static function multipleInsert($objects, $connection = null, $ignore = false) {
		$isConnectionProvided = true;
		if ($connection == null) {
			$connection = DatabaseManager::getDatabaseConnection();
			$isConnectionProvided = false;
		}
		if ($connection != null) {
			if (DatabaseManager::doUpdateQueryWithConnection(ORMapperQuery::getInsertQuery($objects, $connection, false, $ignore), $connection)) {
				if (!$isConnectionProvided) {
					DatabaseManager::closeDatabaseConnection($connection);
				}
				return true;
			}
			else {
				return false;
			}
		}
		else {
			return false;
		}
	}

	/**
	 * Remove object from a database table.
	 * 
	 * @param object $object
	 */
	public static function delete($object, $where = null, $connection = null) {
		$isConnectionProvided = true;
		if ($connection == null) {
			$connection = DatabaseManager::getDatabaseConnection();
			$isConnectionProvided = false;
		}
		$result = DatabaseManager::doUpdateQueryWithConnection(ORMapperQuery::getDeleteQuery($object, $connection, $where), $connection);
		if (!$isConnectionProvided) {
			DatabaseManager::closeDatabaseConnection($connection);
		}
		return $result;
	}

	/**
	 * Gather objects from a database.
	 * 
	 * @param object $object A parameter for specifying a table and a class for gathered objects
	 * @param ORMapperQueryWhere $whereQuery
	 * @param str $orderColumnName
	 * @param number $limit
	 * @param number $offset
	 * @return array Gathered objects
	 */
	public static function select($object,
			$whereQuery = null,
			$orderColumnName = null,
			$limit = null,
			$offset = 0,
			$isForUpdate = false,
			$connection = null) {
		$isConnectionProvided = true;
		if ($connection == null) {
			$connection = DatabaseManager::getDatabaseConnection();
			$isConnectionProvided = false;
		}
		$whereQuery = $whereQuery != null ? $whereQuery : new ORMapperQueryWhere();
		$query = ORMapperQuery::getSelectQuery($object, $connection, $whereQuery, $orderColumnName, $limit, $offset, false, null, $isForUpdate);
//		echo "Query> " . $query . "\n";
//		Logger::log('Qurty: ' . $query);
		if ($isForUpdate) {
			Logger::log($query);
		}
		$objectArray = ORMapper::selectBySQL($object, $query, $connection);
		if (!$isConnectionProvided) {
			DatabaseManager::closeDatabaseConnection($connection);
		}
		return $objectArray;
	}

	/**
	 * Gather objects from a database using a SQL statement.
	 * 
	 * @param object $object A paramter for specifying a class of gathered objects
	 * @param str $query
	 * @param connection $connection
	 * @return array Gathered objects
	 */
	public static function selectBySQL($object, $query, $connection = null) {
		if ($connection == null) {
			$connection = DatabaseManager::getDatabaseConnection();
			$resultArray = DatabaseManager::doSelectQueryWithConnection($query, $connection);
			DatabaseManager::closeDatabaseConnection($connection);
		}
		else {
			$resultArray = DatabaseManager::doSelectQueryWithConnection($query, $connection);
		}
		return ORMapper::_convertClassType($resultArray, $object);
	}
	
	public static function startTransaction($connection) {
		return DatabaseManager::doUpdateQueryWithConnection('start transaction', $connection);
	}
	public static function commit($connection) {
		return DatabaseManager::doUpdateQueryWithConnection('commit', $connection);
	}
	public static function rollback($connection) {
		return DatabaseManager::doUpdateQueryWithConnection('rollback', $connection);
	}

	/**
	 * Gather objects which have distinct values in each specified property. 
	 * 
	 * @param object $object
	 * @param ORMapperQueryWhere $whereQuery
	 * @param str $groupColumnName
	 * @return array Gathered objects
	 */
	public static function selectDistinct($object,
			$whereQuery,
			$groupColumnName) {
		$connection = DatabaseManager::getDatabaseConnection();
		$objectArray = ORMapper::selectBySQL($object,
			ORMapperQuery::getSelectQuery($object, $connection, $whereQuery, null, null, null, true, $groupColumnName),
			$connection);
		DatabaseManager::closeDatabaseConnection($connection);
		return $objectArray;
	}

	/**
	 * Count number of stored objects.
	 * 
	 * @param object $object
	 * @param ORMapperQueryWhere $whereQuery
	 * @return number
	 */
	public static function count($object, $whereQuery = null, $connection = null) {
		$isConnectionProvided = true;
		if ($connection == null) {
			$connection = DatabaseManager::getDatabaseConnection();
			$isConnectionProvided = false;
		}
		$result = DatabaseManager::getCountWithConnection(ORMapperQuery::getCountSelectQuery($object, $connection, $whereQuery), $connection);
		if (!$isConnectionProvided) {
			DatabaseManager::closeDatabaseConnection($connection);
		}
		return $result;
	}

	/**
	 * Get max value of the specifed column.<br/>
	 * The type of the column have to be number.
	 * 
	 * @param object $object
	 * @param str $columnName
	 * @param ORMapperQueryWhere $whereQuery
	 * @return number
	 */
	public static function max($object, $columnName, $whereQuery = null) {
		$connection = DatabaseManager::getDatabaseConnection();
		$result = DatabaseManager::getMaxWithConnection(ORMapperQuery::getMaxSelectQuery($object, $columnName, $connection, $whereQuery), $columnName, $connection);
		DatabaseManager::closeDatabaseConnection($connection);
		return $result;
	}

	/**
	 * Get minimum value of the specifed column.<br/>
	 * The type of the column have to be number.
	 * 
	 * @param object $object
	 * @param str $columnName
	 * @param ORMapperQueryWhere $whereQuery
	 * @return number
	 */
	public static function min($object, $columnName, $whereQuery = null) {
		$connection = DatabaseManager::getDatabaseConnection();
		$result = DatabaseManager::getMinWithConnection(ORMapperQuery::getMinSelectQuery($object, $columnName, $connection, $whereQuery), $columnName, $connection);
		DatabaseManager::closeDatabaseConnection($connection);
		return $result;
	}

	/**
	 * Cast all objects in a result to instances of the target class.
	 * 
	 * @param array $resultArray
	 * @param object $object
	 * @return array Casted objects
	 */
	private static function _convertClassType($resultArray, $object) {
		$className = get_class($object);
		$convertResult = array();
		for ($i = 0; $i < count($resultArray); $i++) {
			$nowObject = new $className();
			foreach ($resultArray[$i] as $key => $value) {
				$nowObject->$key = $value;
			}
			$convertResult[] = $nowObject;
		}
		return $convertResult;
	}

	/**
	 * Return an object.<br/>
	 * Result of the sending query has to include only one row.
	 * 
	 * @param object $object
	 * @param ORMapperQueryWhere $whereQuery
	 * @return object If the result includes 2 or more objects, or if the result does not include an object, null is returned.
	 */
	public static function selectOne($object, $whereQuery, $isForUpdate = false, $connection = null) {
		$result = ORMapper::select($object, $whereQuery, null, null, 0, $isForUpdate, $connection);
		switch (count($result)) {
			case 1:
				return $result[0];
			case 0:
				return null;
			default:
				trigger_error(ORMapperErrorMessages::FAULT_RESULT_COUNT, E_USER_ERROR);
				return null;
		}
	}

	/**
	 * Check whether all properties of the passed object have values which are suitable for related columns in database table. 
	 * 
	 * @param object $object
	 * @return bool
	 */
	public static function validate($object) {
		$objectVars = get_object_vars($object);
		foreach ($objectVars as $varName => $varValue) {
			if (FieldLibrary::isRequiredField($varName) &&
				!isset($varValue)) {
				return false;
			}
			if (isset($varValue) &&
				!FieldLibrary::typeCheck($varName, $varValue)) {
				return false;
			}
		}
		return true;
	}

}

?>
